#!/usr/bin/env python
# vi: et sw=2
#
# Gnome PulseAudio applet
#
# Author: Harry Karvonen <harry.karvonen@gmail.com>
#         Aki Rautio <aki.rautio@gmail.com>
#

import pygtk
pygtk.require('2.0')

import gtk
import gnomeapplet
import gnome.ui
import os.path
import sys

import locale
import gettext

## Global values
APPLET_DIR = '/usr/share/gnome-pulse-applet'
LOCALE_DIR = '/usr/share/locale'
APPLET_VERSION = '0.1'

## Localization
locale.setlocale(locale.LC_ALL, '')

gettext.bindtextdomain('gnome-pulse-applet',LOCALE_DIR)
gettext.textdomain('gnome-pulse-applet')
_ = gettext.gettext

locale.bindtextdomain('gnome-pulse-applet',LOCALE_DIR)
locale.textdomain('gnome-pulse-applet')

from GnomePulseAppletInterface import PulseObj,PulseSinkInputInfo,PulseVolume

################################################################################
#
# PulseApplet
class PulseApplet:

  ##############################################################################
  #
  # Init
  def __init__(self, applet, iid):
    self.applet = applet
    gnome.init("Pulse Applet", self.applet.version)

    self.icon = None
    self.pop = False
   
                        # None == localhost
    self.pulse = PulseObj(None, True) # Connects pulseaudio
    #self.pulse = PulseObj("noHostFound", True) # Connects pulseaudio
    
    ## Applet Icon 
    self.icon = gtk.Image()
    self.icon.set_from_icon_name('audio-volume-medium', 24) 
    self.icon.show()

    ## About window
    self.create_about()


    self.applet.add(self.icon)
    self.applet.connect("button-press-event", self.button_press)
    self.applet.connect("key-press-event", self.key_press)
    self.applet.connect("destroy", self.cleanup, self.applet)
    self.applet.show_all()

    self.dock = PulseAppletDock(self.pulse, self.applet)
    self.dock.window.connect("button-press-event", self.button_press)
    self.dock.window.connect("key-press-event", self.key_press)

    return

  ###

  def pop_dock(self):
    if self.pop:
      self.pop_dock_down()
    else:
      self.pop_dock_up()

    return

  ###

  def pop_dock_up(self):
    if self.pop:
      return

    if not self.pulse.connected:
      self.pulse.reconnect()

    self.dock.update()
    self.applet.set_state(gtk.STATE_SELECTED)

    self.dock.window.show_all()

    gtk.gdk.keyboard_grab(self.dock.window.window, True)
    gtk.gdk.pointer_grab(self.dock.window.window, True,
        gtk.gdk.BUTTON_PRESS_MASK |
        gtk.gdk.BUTTON_RELEASE_MASK |
        gtk.gdk.POINTER_MOTION_MASK
    )

    self.pop = True

    return

  ###

  def pop_dock_down(self):
    if not self.pop:
      return

    self.applet.grab_remove()
    self.dock.window.hide_all()

    self.applet.set_state(gtk.STATE_NORMAL)

    gtk.gdk.keyboard_ungrab()
    gtk.gdk.pointer_ungrab()

    self.pop = False

    return

  ###

  def cleanup(self, event, widget):
    #self.pulse.pulse_disconnect()
    del self.applet
    return

  ###

  ## Mouse button actions
  def button_press(self, widget, event):

    # If we want exit by clicking some where
    # FIXME This may be unused
    if event.window != widget.window and \
       event.type == gtk.gdk.BUTTON_PRESS:
      self.pop_dock_down()
      return True

    elif event.window == widget.window:
      if event.button == 3:
        if self.pop:
          self.pop_dock_down()
          return True
        else:
          self.create_menu()

      elif event.button == 1:
        self.pop_dock()

        return True

    return False

  ###

  def key_press(self, widget, event):
    if not event.type == gtk.gdk.KEY_PRESS:
      return

    if event.keyval == 32: # Space
      self.pop_dock()
    elif event.keyval == 65293: # Enter
      self.pop_dock()
    elif event.keyval == 65307: # ESC
      self.pop_dock_down()

    return True

  ###

  ## About Dialog
  def create_about(self, data = None):
    self.about = gtk.AboutDialog()
    self.about.set_name(_('Gnome PulseAudio applet'))
    self.about.set_version(self.applet.version)
    self.about.set_comments(_('Control each PulseAudio audio streams'))
    self.about.set_authors([
        'Harry Karvonen',
        'Aki Rautio'
    ])
    self.about.set_artists(['Petri Partio'])
    self.about.set_website("http://code.google.com/p/gnome-pulse-applet/")
    self.about.connect('response', self.about_response)
    self.about.connect('close', self.about_close)
    return

  def show_about(self,event,data = None):
    self.about.show()
    return
    

  ###

  def about_response(self, dialog, response):
    if response < 0:
      dialog.hide()
      dialog.emit_stop_by_name('response')
    return

  ###

  def about_close(self, widget):
    self.about.hide()
    return True

  ###

  ## Create menu
  def create_menu(self):
    self.verbs = [("about", self.show_about)]
    self.applet.setup_menu_from_file(APPLET_DIR,"/gnome-pulse-applet.xml",None, self.verbs)
    return

################################################################################
#
# PulseAppletDock

class PulseAppletDock:
  def __init__(self, pulse, applet):
    self.applet = applet
    self.pulse = pulse
    self.window = gtk.Window(gtk.WINDOW_POPUP)

    self.streams = gtk.VBox(False)

    eBox = gtk.EventBox()
    eBox.add(self.streams)

    dockFixed = gtk.Fixed()
    dockFixed.put(eBox, 5, 0)

    dockFrame = gtk.Frame()
    dockFrame.set_shadow_type(gtk.SHADOW_OUT)
    dockFrame.add(dockFixed)

    # Prevent hiding stream list when clicking stream name
    def eat_button_event(widget, event):
      return True

    # Prevent hiding stream list when clicking stream name
    eBox.connect("button-press-event", eat_button_event)

    self.window.add(dockFrame)
    return

  ###

  def clearStreamsBox(self):
    if self.streams != None:
      self.streams.foreach(self.streams.remove)
    return

  ###

  def addEmptyNotice(self):
    l = gtk.Label()
    l.set_markup("<span weight='bold'>" +_('No Streams') + "</span>")
    self.streams.add(l)

    return

  ###

  def addNoConnection(self):
    l = gtk.Label()
    l.set_markup("<span weight='bold'>" + _('No Connection') + "</span>")
    self.streams.add(l)

    return

  ###
  
  def addStreamSeparator(self):
    streamSeparator = gtk.HSeparator()
    self.streams.add(streamSeparator)
    return

  ###

  def addStreamObject(self, sink):
    stream = PulseAppletStream(sink)

    stream.muteButton.connect("button-press-event",
                              self.un_mute_stream_proto,
                              sink.index)

    stream.volumeAdjust.connect("value-changed",
                                self.set_stream_volume,
                                sink.index)

    self.streams.add(stream)

    return

  ###

  def update(self):

    if self.pulse.connected:
      sinks = self.pulse.pulse_sink_input_list()

      if sinks == None:
        sinks = []

      clients = dict(map(lambda x: (x.index, x),
                                        self.pulse.pulse_client_list()))

      # Set client
      map(lambda x: x.setClient(clients[x.client_id]), sinks)
    else:
      sinks = []

    ## Streams amount, stream names, stream attributes needed
    streamsAmount = len(sinks)

    self.clearStreamsBox()

    if streamsAmount == 0:
      if self.pulse.connected:
        self.addEmptyNotice()
      else:
        self.addNoConnection()
      height = 20
    else:
      streamNumber = 1
      for sink in sinks:
        self.addStreamObject(sink)

        ## Create Horizontal bar in the end if stream isn't last one
        if streamNumber < streamsAmount:
          self.addStreamSeparator()

        streamNumber = streamNumber + 1

      height = (streamsAmount * 97)

    self.window.resize(200, height)

    ##Update position
    self.updatePosition()
    
    return

  ###

  def updatePosition(self):
    self.window.set_position(gtk.WIN_POS_MOUSE)

    # Current position on screen
    (appletX, appletY) = self.applet.window.get_origin()
    (appletWidth, appletHeight) = self.applet.window.get_size()
    (dockWidth, dockHeight) = self.window.get_size()

    orient = self.applet.get_orient()

    if orient == gnomeapplet.ORIENT_UP:
      x = appletX - int(dockWidth / 2) + int(appletWidth / 2)
      y = appletY - dockHeight
    elif orient == gnomeapplet.ORIENT_RIGHT:
      x = appletX + appletWidth
      y = appletY - int(dockHeight / 2) + (appletHeight / 2)
    elif orient == gnomeapplet.ORIENT_DOWN:
      x = appletX - int(dockWidth / 2) + int(appletWidth / 2)
      y = appletY + appletHeight
    else:
      x = appletX - dockWidth
      y = appletY - int(dockHeight / 2) + (appletHeight / 2)

    self.window.move(x,y)

    return

  ####

  ## Mute/Unmute Action
  def un_mute_stream_proto(self, widget, event, ix):
    widget_icon = gtk.Image()
    
    ## widget.muted tells streams state 
    if widget.muted == False:
      self.pulse.pulse_mute_stream(ix)
      widget_icon.set_from_icon_name('audio-volume-muted', 36)
      widget.set_image(widget_icon)
      widget.muted = True
    else:
      self.pulse.pulse_unmute_stream(ix)
      widget_icon.set_from_icon_name('audio-volume-medium', 36)
      widget.muted = False

    widget.set_image(widget_icon) 
    return

  ###

  def set_stream_volume(self, widget, ix):
    print "set_stream_volume: callback"
    self.pulse.pulse_set_sink_input_volume(ix,
        PulseVolume(int(widget.get_value())))
    return

##############

# This is one stream.
class PulseAppletStream(gtk.Fixed):
  def __init__(self, pulseSink):
    gtk.Fixed.__init__(self)

    # PROVIDES
    self.muteButton = None
    self.volumeAdjust = None

    ## Create label for Stream name
    if len(pulseSink.name) < 30:
      streamName = pulseSink.name
    else:
      streamName = pulseSink.name[0:27] + "..."
    
    streamLabel = gtk.Label()
    streamLabel.set_markup("<span size='x-small'>" + streamName + "</span>")
    
    ## If Streams client name exits, create label for client name
    if pulseSink.client != None:
      streamClientLabel = gtk.Label()
      streamClientLabel.set_markup("<span size='small' weight='bold'>" +
                                   pulseSink.client.name + ":</span>")
      self.put(streamClientLabel,0,5)
      streamPosition = 25
    
    else:
      streamPosition = 5
    
    self.put(streamLabel, 0, streamPosition)
    
    ## Create Mute/UnMute button
    muteButton = gtk.Button()
    muteButtonIcon = gtk.Image()
    
    if pulseSink.mute == 0:
      ##stream_boolean.set_label("Mute")
      muteButtonIcon.set_from_icon_name('audio-volume-medium', 36)
      muteButton.muted = False
    else:
      ##stream_boolean.set_label("Unmute")
      muteButtonIcon.set_from_icon_name('audio-volume-muted', 36)
      muteButton.muted = True
      
    muteButton.set_image(muteButtonIcon)
    
    self.put(muteButton, 0, streamPosition + 25)
    
    
    ## Create Volumebar
    volumeLevel = sum(pulseSink.volume.values) / len(pulseSink.volume.values)
    volumeAdjust = gtk.Adjustment(volumeLevel, 0, 100, -5)

    streamVolume = gtk.HScale(volumeAdjust)
    streamVolume.set_digits(0)
    streamVolume.set_property("width-request", 160)
    streamVolume.set_value_pos(gtk.POS_BOTTOM)
    self.put(streamVolume, 30, streamPosition + 25)

    self.muteButton = muteButton
    self.volumeAdjust = volumeAdjust
    return

######

def pulse_applet_factory(applet, iid):
  applet.version = APPLET_VERSION
  PulseApplet(applet, iid)
  return True

if len(sys.argv) == 2 and sys.argv[1] == "run-in-window":
  main_window = gtk.Window(gtk.WINDOW_TOPLEVEL)
  main_window.set_title("Pulse Applet")
  main_window.connect("destroy", gtk.main_quit)
  app = gnomeapplet.Applet()
  pulse_applet_factory(app, None)
  app.reparent(main_window)
  main_window.show()
  gtk.main()

else:
  #import sys
  #sys.stdout = open("/tmp/pulse-applet.log", "w")
  #sys.stderr = open("/tmp/pulse-applet.log", "w+")

  gnomeapplet.bonobo_factory("OAFIID:GNOME_PulseApplet_Factory", 
                             gnomeapplet.Applet.__gtype__, 
                             "pulse-applet", "0", pulse_applet_factory)
